3D オブジェクトのアニメーション ( Android OpenGL フレームワーク “Rajawali” と戯れる #08 )
Android OpenGL フレームワーク "Rajawali" と戯れるシリーズ
第 08 回目は、3D オブジェクトのアニメーションについて解説します。
Rajawali がサポートするアニメーション可能な 3D フォーマット
Rajawali では下記フォーマットをサポートしています。
- MD2 (*.md2)
- MD5 (*.md5mesh, *.md5anim) (*.md5camera は非サポート )
MD2 は QuakeII という FPS(First Person shooter) ゲームで使用されている頂点アニメーション情報を持った 3D オブジェクトフォーマットで、MD5 は DOOM3 という同じく FPS ゲームで使用されているボーンやウェイトなどの情報を保持した 3D オブジェクトフォーマットです。いずれも id Software 社によって開発されています。
MD5 の読み込み
MD5 は Blender やその他の 3D ツールで書き出せるらしいのですが、手持ちの LightWave 3D では上手く書き出せなかったので、DOOM3 を買ってインストールしてゲームのデータを拝借してみました。
出力結果
動きますね。
3D オブジェクトおよびアニメーション情報のパースとデータ取得
RajawaliRenderer 内で以下のように記述します。
//第一引数の文字列は任意のアニメーション名、R.raw.idle は idle.md5anim の ID MD5AnimParser animParser = new MD5AnimParser("test", this, R.raw.idle); animParser.parse(); //R.raw.skeleton は skeleton.md5mesh の ID MD5MeshParser meshParser = new MD5MeshParser(this, R.raw.skeleton); meshParser.parse(); BoneAnimationSequence md5anim = (BoneAnimationSequence) animParser.getParsedAnimationSequence(); BoneAnimationObject3D md5mesh = (BoneAnimationObject3D) meshParser.getParsedAnimationObject(); md5mesh.setAnimationSequence(md5anim); md5mesh.play(); addChild(md5mesh);
シンプルですね。
ちなみに、テクスチャは .md5mesh 内に含まれている情報を参照する作りになっています。
MD2 の読み込み
RajawaliRenderer 内で以下のように記述します。MD5 よりシンプルです。
MD2Parser parser = new MD2Parser(mContext.getResources(), mTextureManager, R.raw.r_miku_md2); parser.parse(); VertexAnimationObject3D mObj = (VertexAnimationObject3D) parser.getParsedAnimationObject(); mObj.play(); addChild(mObj);
LightWave 3D で MD2 ファイル書き出し
MD5 を自前で書き出すことは失敗しましたが MD2 は書き出せたので、備忘録を兼ねて手順を残しておきます。
下準備
前回に引き続き、三次元CG@七葉にて保管されているズサ氏制作のはちゅねミク 3D オブジェクト+テクスチャ データ を元に作業を進めます。
「モデラー」でボーンを入れて…
「レイアウト」でボーンを動かし、キーフレームをセットします。
これらの作業で 1 点注意しなければならないのは座標系の違いです。
Android の座標系と LightWave 3D の座標系は異なるので、モデリング作業が終わった段階で X 方向に鏡面コピーした 3D オブジェクトを「レイアウト」に送っています。
使用プラグイン
アニメーション情報を保持した MD2 ファイルは「レイアウト」を使用して書きだしますが、デフォルトの機能では MD2 を書き出すことはできないので、有志による MD2 書出しプラグインを使用します。( 32bit 版でのみ動作します )
MD2 Import/Export Tools の設定項目
「レイアウト」に送ったオブジェクトのアイテムプロパティから「変位」タブ内にある「変位プラグイン追加」を選択した後、「MRB::Export::Quake2」を選択します。
選択した後、ダイアログの下端に表示される「Edit」ボタンをクリックすると「Construction Panel」が表示されるので、ディレクトリ情報やテクスチャ情報、キーフレームのアニメーション情報を入力します。
設定が完了したら「レイアウト」の「ユーティリティ」タブ→「プラグイン」から「MRB::ExportControl」を選択してダイアログを表示します。
データグリッドの Save 項目にチェックを入れ、「Save Checked Models」をクリックすると、「プレビュー作成」のダイアログが表示されるので、必要に応じてフレームを調整したうえで「OK」をクリックします。
その後「Preview Playback Controls」ダイアログが表示されるので、一度再生させてプレビューを終了させた段階で MD2 ファイルが書き出されます。
出力結果
ムービー
ねんがんのねぎふりができたぞ!
ソース
MainActivity.java
package jp.classmethod.sample;
import java.util.ArrayList; import javax.microedition.khronos.opengles.GL10; import rajawali.BaseObject3D; import rajawali.RajawaliFragmentActivity; import rajawali.animation.mesh.BoneAnimationObject3D; import rajawali.animation.mesh.BoneAnimationSequence; import rajawali.animation.mesh.VertexAnimationObject3D; import rajawali.lights.DirectionalLight; import rajawali.parser.MD2Parser; import rajawali.parser.md5.MD5AnimParser; import rajawali.parser.md5.MD5MeshParser; import rajawali.renderer.RajawaliRenderer; import android.content.Context; import android.content.res.Resources; import android.os.Bundle;
public class MainActivity extends RajawaliFragmentActivity {
private RajawaliRenderer mRenderer;
@Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); mRenderer = new Lesson06_01Renderer(this); mRenderer.setSurfaceView(mSurfaceView); setRenderer(mRenderer); }
/** * RajawaliRenderer のサブクラス */ private class Lesson06_01Renderer extends RajawaliRenderer { /** パースされた 3D オブジェクトデータ */ private VertexAnimationObject3D mObj;
/** パースされた 3D オブジェクトデータのリスト */
private ArrayList
/** コンストラクタ */ public Lesson06_01Renderer(Context context) { super(context); setFrameRate(60); setBackgroundColor(0x999999); }
@Override /** シーン初期化 */ protected void initScene() {
mCamera.setX(6.5f); mCamera.setY(1.7f); mCamera.setZ(-5f); mCamera.setRotX(10); mCamera.setRotY(-55); mCamera.setRotZ(10);
Resources r = mContext.getResources();
DirectionalLight light = new DirectionalLight(); light.setPower(1.5f); light.setColor(0xff9900); light.setPosition(0f, 3f, -1f);
MD2Parser parser = new MD2Parser(r, mTextureManager, R.raw.r_miku_md2); parser.parse();
mObj = (VertexAnimationObject3D)parser.getParsedAnimationObject(); mObj.addLight(light);
int i, j, l = 6, m = 6; float startPos = -5f; float posX; float posZ; float offset = 2f; VertexAnimationObject3D tmp; posX = startPos; for(i = 0; i < l; i++) { posZ = startPos; for(j = 0; j < m; j++) { tmp = mObj.clone(); tmp.setPosition(posX, 0, posZ); mList.add(tmp); posZ += offset; } posX += offset; } for(i = 0; i < mList.size(); i++) { tmp = mList.get(i); addChild(tmp); tmp.play(true); } } @Override /** フレーム描画 */ public void onDrawFrame(GL10 glUnused) { super.onDrawFrame(glUnused); int i; for(i = 0; i < mList.size(); i++) { rotateObject(mList.get(i)); } } /** * 3D オブジェクトの回転 * @param obj 任意の BaseObject3D オブジェクト */ protected void rotateObject(BaseObject3D obj) { obj.setRotation(0, obj.getRotY() + 1f, 0); } } } [/java]
次回
RajawaliRenderer と UI について解説します。